// Copyright 2013 The Go Circuit Project
// Use of this source code is governed by the license for
// The Go Circuit Project, found in the LICENSE file.
//
// Authors:
//   2014 Chris Monson <chris@gocircuit.org>

package ripple

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"circuit/kit/iomisc"
	"circuit/kit/rh"
	"circuit/kit/rh/ns"
)

func buildChanHelp(name string, id uint64) string {
	return fmt.Sprintf(chanHelpFmt, path.Join("/", name), id)
}

const chanHelpFmt = `
  This is a channel ripple, named "%s", with QID ⟨Q%016x⟩.

  SYNOPSIS

  First you must "make" the channel by providing the buffer size:

  echo "0" >>

  If you do not do this before acting on the endpoints below, it is the same as
  attempting to operate on a nil channel in Go: everything blocks (and
  non-blocking operations return errors).

  Once made, the channel can be read via "read" and written via "write", thus:

  echo hello >> send    # Send data on the channel. Blocks when buffer is full and no receivers.
  echo hello >> trysend # Non-blocking send, gives an EBUSY error if it would block.
  cat recv              # Receive data from the channel. Blocks until data is available.
  cat tryrecv           # Non-blocking recv, gives an EBUSY error if it would block.
  echo close >> close   # Close the channel.
  cat stat              # Read a description of the channel status.
  cat help              # Read this help screen.

  Note that the send protocol is to open, write, then close. Closing the write
  file triggers the final send, and defines an atomic unit of data. Both send
  and recv block on open if there is not something ready in the buffer or
  someone on the other end of the channel.

  More specifically, send blocks on "open" if the buffer is full with no
  receivers. As soon as either condition changes, open unblocks.

  Recv also blocks on open if there are no items in the buffer and no waiting
  senders. When a sender successfully opens, recv's open unblocks. It then
  blocks on read until the sending end closes the "send" file, at which point
  data is actually available (if there is any).

  It is valid to open and close without sending any data, equivalent to Go's

    ch <- struct{}{}

  Similarly, it is valid to open and close without receiving any data, equivalent to

    <-ch

`

type Chan struct {
	sync.Mutex

	// underlying channel implementation
	hooks chan chan []byte

	q    rh.Q
	name string
	gtm  time.Time

	capFID     rh.FID
	recvFID    rh.FID
	sendFID    rh.FID
	tryrecvFID rh.FID
	trysendFID rh.FID
	closeFID   rh.FID
	statFID    rh.FID
	helpFID    rh.FID

	bytesRead      int64
	recordsRead    int64
	bytesWritten   int64
	recordsWritten int64
	closed         bool
}

func NewChan(name string) (*Chan, rh.FID) {
	now := time.Now()
	c := &Chan{
		q: rh.Q{
			ID:  uint64(rand.Int63()),
			Ver: 1,
		},
		name: name,
		gtm:  now,
	}

	c.sendFID = NewChanSend(c.SendHooks, c.reportSend, false)
	c.trysendFID = NewChanSend(c.SendHooks, c.reportSend, true)
	c.recvFID = NewChanRecv(c.RecvHooks, c.reportRecv, false)
	c.tryrecvFID = NewChanRecv(c.RecvHooks, c.reportRecv, true)

	c.capFID = NewConfigFID(
		"cap",
		func(b []byte) error {
			v, err := strconv.Atoi(strings.TrimSpace(string(b)))
			if err != nil {
				return fmt.Errorf("Invalid capacity spec: %q", string(b))
			}
			if v < 0 {
				v = 0
			}
			return c.SetCapacity(v)
		},
		func() []byte {
			return []byte(fmt.Sprintf("%d", c.Capacity()))
		},
	)

	c.closeFID = ns.RegulateFork(func() ns.UnregulatedFID {
		fid, r := ns.NewPipeWriterFID("close")
		go func() {
			body, err := ioutil.ReadAll(io.LimitReader(r, maxCommandLen))
			if err != nil {
				return
			}
			if strings.TrimSpace(string(body)) != "close" {
				return
			}
			c.Close()
		}()
		return fid
	})

	c.statFID = ns.RegulateFork(func() ns.UnregulatedFID {
		rw, rr, bw, br, closed := c.Stat()
		var buffstr string
		if c.hooks == nil {
			buffstr = "none"
		} else {
			buffstr = fmt.Sprintf("%d", cap(c.hooks))
		}
		body := fmt.Sprintf(
			"Capacity: %s\nBytesWritten: %d\nRecordsWritten: %d\n"+
				"BytesRead: %d\nRecordsRead: %d\nClosed: %v\n", buffstr, bw, rw, br, rr, closed)
		r := bytes.NewReader([]byte(body))

		return ns.NewReaderFID("chan/stat", iomisc.ReaderNopCloser(r))
	})

	c.helpFID = NewHelp(buildChanHelp(name, uint64(rand.Int63())))

	return c, c.Walk()
}

func (c *Chan) RecvHooks() <-chan (chan []byte) {
	defer un(lock(c))
	return c.hooks
}

func (c *Chan) SendHooks() chan<- (chan []byte) {
	defer un(lock(c))
	return c.hooks
}

func (c *Chan) Capacity() int {
	defer un(lock(c))
	if c.hooks == nil {
		return -1
	}
	return cap(c.hooks)
}

func (c *Chan) SetCapacity(newCap int) error {
	defer un(lock(c))

	if c.hooks != nil {
		return fmt.Errorf("Can't set capacity twice.")
	}

	c.hooks = make(chan chan []byte, newCap)
	return nil
}

func (c *Chan) Walk() rh.FID {
	return ns.NewFID("chan", &chanDir{c})
}

func (c *Chan) Read(p []byte) (n int, err error) {
	return 0, rh.ErrPerm
}

func (c *Chan) Write(p []byte) (n int, err error) {
	return 0, rh.ErrPerm
}

func (c *Chan) Close() (err error) {
	defer un(lock(c))
	if c.closed {
		return rh.ErrGone
	}
	source := make(chan []byte)
	close(source)
	clean := false
	for !clean {
		select {
		case <-c.hooks:
			// Get a channel from the writer, if it is waiting.
		case c.hooks <- source:
			// Send a closed source to the reader if it is waiting for one.
		default:
			// All done. Nothing left to clean up.
			clean = true
		}
	}
	close(c.hooks)
	c.closed = true
	return nil
}

func (c *Chan) reportSend(bytes int64) {
	defer un(lock(c))
	c.bytesWritten += bytes
	c.recordsWritten++
}

func (c *Chan) reportRecv(bytes int64) {
	defer un(lock(c))
	c.bytesRead += bytes
	c.recordsRead++
}

func (c *Chan) Stat() (nwrec, nrrec, nwbytes, nrbytes int64, closed bool) {
	defer un(lock(c))
	return c.recordsWritten, c.recordsRead, c.bytesWritten, c.bytesRead, c.closed
}

//
type chanDir struct {
	c *Chan
}

func (cd *chanDir) Help() rh.FID {
	return cd.c.helpFID
}

func (cd *chanDir) Recv() rh.FID {
	return cd.c.recvFID
}

func (cd *chanDir) Send() rh.FID {
	return cd.c.sendFID
}

func (cd *chanDir) Tryrecv() rh.FID {
	return cd.c.tryrecvFID
}

func (cd *chanDir) Trysend() rh.FID {
	return cd.c.trysendFID
}

func (cd *chanDir) Close() rh.FID {
	return cd.c.closeFID
}

func (cd *chanDir) Stat() rh.FID {
	return cd.c.statFID
}

func (cd *chanDir) Cap() rh.FID {
	return cd.c.capFID
}
